using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Nuts
{
    public class ShadowHelper
    {
        const float MIN_FACTOR = 0.2f;
        const int ALTITUDE_FOR_MIN_FACTOR = 80;
        const float MIN_SHADOW_ALPHA = 0.0f;
        const float MAX_SHADOW_ALPHA = 0.5f;


        private struct Shadow
        {
            public Rectangle spot;
            public byte alpha;
        }

        public ShadowHelper()
        {
            _shadows = new List<Shadow>();
        }
        
        public void LoadContent(Game1 game)
        {
            _spotTex = game.Content.Load<Texture2D>("shadow_spot");
        }
        
        public void AddShadow(int x, int y_bottom, int base_width, int base_thickness)
        {
            Shadow new_shadow = new Shadow();
            int altitude = Math.Max(0, Game1.GROUND_LEVEL - y_bottom);

            // compute size reduction factor due to altitude
            // lerp from 1.0 to 0.5
            float ratio = Math.Min(1.0f, Math.Max(0.0f, (float)altitude/(float)ALTITUDE_FOR_MIN_FACTOR ));
            float size_factor = ratio * MIN_FACTOR +  (1.0f - ratio);
            
            // compute alpha fade factor due to altitude
            float alpha_factor = ratio * MIN_SHADOW_ALPHA + (1.0f - ratio) * MAX_SHADOW_ALPHA;
            
            // compute shadow spot rectangle
            int width = (int)(size_factor * (float)base_width);
            int height = (int)(size_factor * (float)base_thickness);
            new_shadow.spot = new Rectangle(x - width / 2, Game1.GROUND_LEVEL - height / 2, width, height);
            new_shadow.alpha = (byte)(255.0f * alpha_factor);

            // add shadow
            _shadows.Add(new_shadow);
        }

        public void DrawShadows(SpriteBatch spriteBatch)
        {
            Color shadow_color = new Color(255,255,255, 255);
            foreach(Shadow shadow in _shadows)
            {
                shadow_color.R = shadow.alpha;
                shadow_color.G = shadow.alpha;
                shadow_color.B = shadow.alpha;
                shadow_color.A = shadow.alpha;
                spriteBatch.Draw(_spotTex, shadow.spot, shadow_color);
            }

            _shadows.Clear();
        }

        List<Shadow>    _shadows;
        Texture2D       _spotTex;
    }

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        public const int SCREEN_WIDTH = 800;
        public const int SCREEN_HEIGHT = 480;
        // graphics.GraphicsDevice.Viewport.Width

        public const int GROUND_LEVEL = 425;
        public const int MAX_NUTS = 10;

        const bool DRAW_DEBUG = false;

        GraphicsDeviceManager   graphics;
        SpriteBatch             spriteBatch;
        SpriteFont              fontDebug;
        Texture2D               texBackground;
        Texture2D               texItems00;
        Texture2D               texCursor;
        Texture2D               texDebugSolid;
        MouseState              previousMouseState;
        KeyboardState           previousKeyboardState;
        Squirrel                squirrel;
        List<Nut>               nuts;
        NutSpawner[]            nutSpawners;
        List<Birds.BirdTest>    birds;
        ShadowHelper            shadowHelper;
        Random                  randomGenerator;

        SoundEffect soundHit;
        SoundEffect soundMiss;

        readonly Rectangle SrcRectGrass00 = new Rectangle(1, 0, 90, 40);
        readonly Rectangle SrcRectGrass01 = new Rectangle(94, 0, 60, 50);
        readonly Rectangle SrcRectBush = new Rectangle(0, 55, 190, 120);
        readonly Rectangle SrcRectBushPart = new Rectangle(130, 119, 30, 30);
        readonly Rectangle SrcRectSouche = new Rectangle(2, 180, 90, 75);

        readonly Vector2 GrassPos0 = new Vector2(80, 400);
        readonly Vector2 GrassPos1 = new Vector2(200, 430);
        readonly Vector2 GrassPos2 = new Vector2(550, 395);
        readonly Vector2 GrassPos3 = new Vector2(680, 420);

        readonly Vector2 BushPos0 = new Vector2(175, 385);
        readonly Vector2 BushPos1 = new Vector2(375, 385);
        readonly Vector2 BushPos2 = new Vector2(575, 385);

        readonly Vector2 SouchePos0 = new Vector2(-17, 367);
        readonly Vector2 SouchePos1 = new Vector2(727, 367);

        public readonly Vector2 SAFE_SPOT_LEFT = new Vector2(-20, GROUND_LEVEL);
        public readonly Vector2 SAFE_SPOT_RIGHT = new Vector2(820, GROUND_LEVEL);

        Bush[] m_bushes;
        BushConfig m_bushConfig;

        public Game1()
        {
            randomGenerator = new Random();

            graphics = new GraphicsDeviceManager(this);
            graphics.IsFullScreen = false;
            graphics.PreferredBackBufferWidth = SCREEN_WIDTH;
            graphics.PreferredBackBufferHeight = SCREEN_HEIGHT;

            Content.RootDirectory = "Content";

            shadowHelper = new ShadowHelper();

            squirrel = new Squirrel(this);
            nuts = new List<Nut>();
            nutSpawners = new NutSpawner[6];

            nutSpawners[0] = new NutSpawner( 80, 160, this, randomGenerator);
            nutSpawners[1] = new NutSpawner(160, 110, this, randomGenerator);
            nutSpawners[2] = new NutSpawner(250,  65, this, randomGenerator);

            nutSpawners[3] = new NutSpawner(450, 70, this, randomGenerator);
            nutSpawners[4] = new NutSpawner(550, 10, this, randomGenerator);
            nutSpawners[5] = new NutSpawner(650, 120, this, randomGenerator);

            // bird test
            birds = new List<Birds.BirdTest>();
            birds.Add(new Birds.BirdTest(this)); // ###
            //birds.Add(new Birds.BirdTest(this)); // ###
            //birds.Add(new Birds.BirdTest(this)); // ###
            //birds.Add(new Birds.BirdTest(this)); // ###

            // Bush
            m_bushConfig = new BushConfig(4);

            m_bushConfig.layers[0] = new BushLayerConfig();
            m_bushConfig.layers[0].srcRect = SrcRectBush;
            m_bushConfig.layers[0].position = new Vector2((float)(-SrcRectBush.Width / 2), (float)(-SrcRectBush.Height));

            m_bushConfig.layers[1] = new BushLayerConfig();
            m_bushConfig.layers[1].srcRect = SrcRectBushPart;
            m_bushConfig.layers[1].position = new Vector2(50.0f, -90.0f);

            m_bushConfig.layers[2] = new BushLayerConfig();
            m_bushConfig.layers[2].srcRect = SrcRectBushPart;
            m_bushConfig.layers[2].position = new Vector2(-50.0f, -90.0f);

            m_bushConfig.layers[3] = new BushLayerConfig();
            m_bushConfig.layers[3].srcRect = SrcRectBushPart;
            m_bushConfig.layers[3].position = new Vector2(0.0f, -100.0f);

            m_bushes = new Bush[3];

            m_bushes[0] = new Bush(m_bushConfig, BushPos0);
            m_bushes[1] = new Bush(m_bushConfig, BushPos1);
            m_bushes[2] = new Bush(m_bushConfig, BushPos2);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            previousMouseState = Mouse.GetState();
            previousKeyboardState = Keyboard.GetState();

            squirrel.Initialize();

            foreach(Birds.BirdTest bird in birds)
            {
                bird.Initialize();
            }

            foreach (Bush bush in m_bushes)
            {
                bush.Initialize();
            }

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // create graphical objects
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // create debug resources

            // 1x1 solid white texture
            texDebugSolid = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
            Int32[] pixel = { 0xFFFFFF };
            texDebugSolid.SetData<Int32>(pixel, 0, texDebugSolid.Width * texDebugSolid.Height);


            // load resources
            texBackground = this.Content.Load<Texture2D>("background");
            fontDebug = this.Content.Load<SpriteFont>("debug_font");
            texItems00 = this.Content.Load<Texture2D>("items_00");
            texCursor = this.Content.Load<Texture2D>("Cursor");
            soundHit = this.Content.Load<SoundEffect>("sound_hit");
            soundMiss = this.Content.Load<SoundEffect>("sound_miss");

            m_bushConfig.texture = this.Content.Load<Texture2D>("items_00");
            m_bushConfig.sound = this.Content.Load<SoundEffect>("grass1");

            shadowHelper.LoadContent(this);

            squirrel.LoadContent();
            Nut.LoadContent(this);
            Birds.BirdTest.LoadContent(this);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // query input states
            KeyboardState kb = Keyboard.GetState();
            MouseState mouse = Mouse.GetState();

            int X = mouse.X;
            int Y = mouse.Y;

            // Allows the game to exit
            if ( CheckExit() )
                this.Exit();

            // update nuts spawners            
            foreach (NutSpawner spawner in nutSpawners)
            {
                spawner.Update(gameTime);
            }

            // update nuts
            foreach (Nut nut in nuts)
            {
                nut.Update(gameTime);
            }

            // update birds
            foreach(Birds.BirdTest bird in birds)
            {
                bird.SetTarget(X, Y); // ### follow me flock!
                bird.Update(gameTime);
            }

            // update bushes
            foreach (Bush bush in m_bushes)
            {
                bush.Update(gameTime);
            }

            // update squirrel
            squirrel.Update(gameTime);

            // detect mouse hit
            if ( mouse.LeftButton == ButtonState.Pressed && previousMouseState.LeftButton == ButtonState.Released )
            {
                // hit detection on active entities

                bool hit = false;

                // check if hit is on squirrel :D
                if (squirrel.GetHitBox().Contains(X, Y))
                {
                    if(Keyboard.GetState().IsKeyDown(Keys.G))
                    {
                        squirrel.BeginGrab();
                    }
                    else if(Keyboard.GetState().IsKeyDown(Keys.S))
                    {
                        squirrel.Stun();
                    }
                    else
                    {
                        squirrel.OnSlap(X, Y);
                    }
                    hit = true;
                }

                // birds
                if(!hit)
                {
                    foreach(Birds.BirdTest bird in birds)
                    {
                        if (bird.OnSlap(X,Y))
                        {
                            hit = true;
                            break;
                        }
                    }
                }

                // nut spawners
                if ( !hit )
                {
                    foreach (NutSpawner spawner in nutSpawners)
                    {
                        if (spawner.OnSlap(X,Y))
                        {
                            hit = true;
                            break;
                        }
                    }
                }

                // bushes
                if ( !hit )
                {
                    for (uint bushIt = 0; bushIt < m_bushes.Length; ++bushIt)
                    {
                        if (m_bushes[bushIt].OnSlap(X, Y))
                        {
                            hit = true;

                            break;
                        }
                    }
                }

                // nothing hit
                if ( !hit )
                {
                    OnMiss(X, Y);
                }
            }

            // debug grab of squirrel
            if( kb.IsKeyDown(Keys.G) )
            {
                if(!previousKeyboardState.IsKeyDown(Keys.G))
                    squirrel.BeginGrab();

                squirrel.GrabSetPos(mouse.X, mouse.Y);
            }
            else
            {
                if(previousKeyboardState.IsKeyDown(Keys.G))
                    squirrel.EndGrab();
            }


            base.Update(gameTime);

            previousMouseState = mouse;
            previousKeyboardState = kb;
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            MouseState ms = Mouse.GetState();

            string dbg_output = string.Format("MousePos {0}, {1}", ms.X, ms.Y);

            // draw background
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque);
                spriteBatch.Draw(texBackground, new Vector2(0.0f, 0.0f), Color.White);
            spriteBatch.End();

            // add entities
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
            {
                // bushes first in the background
                foreach (Bush bush in m_bushes)
                {
                    bush.Draw(gameTime, spriteBatch);
                }

                shadowHelper.DrawShadows(spriteBatch);

                squirrel.Draw(gameTime, spriteBatch);

                foreach (Nut nut in nuts)
                {
                    nut.Draw(gameTime, spriteBatch);
                }

                foreach(Birds.BirdTest bird in birds)
                {
                    bird.Draw(gameTime, spriteBatch);
                }

                // and foreground
                spriteBatch.Draw(texItems00, SouchePos0, SrcRectSouche, Color.White);
                spriteBatch.Draw(texItems00, SouchePos1, SrcRectSouche, Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.FlipHorizontally, 0.0f);

                spriteBatch.Draw(texItems00, GrassPos0, SrcRectGrass01, Color.White);
                spriteBatch.Draw(texItems00, GrassPos1, SrcRectGrass00, Color.White);
                spriteBatch.Draw(texItems00, GrassPos2, SrcRectGrass00, Color.White);
                spriteBatch.Draw(texItems00, GrassPos3, SrcRectGrass01, Color.White);


                // debug draw on top of everything
                if(DRAW_DEBUG)
                {
                    foreach(Bush bush in m_bushes)
                    {
                        spriteBatch.Draw(texDebugSolid, bush.HitBox, Color.Brown);
                    }

                    foreach(NutSpawner spawner in nutSpawners)
                    {
                        spriteBatch.Draw(texDebugSolid, spawner.GetHitBox(), Color.Violet);
                    }

                    foreach(Nut nut in nuts)
                    {
                        spriteBatch.Draw(texDebugSolid, nut.GetHitBox(), Color.Green);
                    }

                    foreach(Birds.BirdTest bird in birds)
                    {
                        spriteBatch.Draw(texDebugSolid, bird.GetHitBox(), Color.AliceBlue);
                    }

                    spriteBatch.Draw(texDebugSolid, squirrel.GetHitBox(), Color.Orange);
                    spriteBatch.Draw(texDebugSolid, squirrel.GetCollectBox(), Color.Red);
                }
            }
            spriteBatch.End();



            // add text and cursor
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
            {
                spriteBatch.Draw(texCursor, new Rectangle(ms.X, ms.Y, 20, 20), Color.White);
                spriteBatch.DrawString(fontDebug, dbg_output, new Vector2(10.0f, 10.0f), Color.Red);

                foreach(Birds.BirdTest bird in birds)
                {
                    bird.DrawDebugInfos(spriteBatch, fontDebug);
                }
            }
            spriteBatch.End();


            base.Draw(gameTime);
        }

        public int GetActiveNutsCount()
        {
            return nuts.Count();
        }

        public List<Nut> GetNuts()
        {
            return nuts;
        }

        public void AddNut(Nut new_nut)
        {
            nuts.Add(new_nut);
        }

        public ShadowHelper GetShadowHelper()
        {
            return shadowHelper;
        }

        public float RandomFloat(float min_value, float max_value)
        {
            return MathHelper.Lerp(min_value, max_value, (float)randomGenerator.NextDouble());
        }

        private bool CheckExit()
        {
            bool pad_exit = (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed);
            bool keyboard_exit = Keyboard.GetState().IsKeyDown(Keys.Escape);
            return (pad_exit || keyboard_exit);
        }

        private void OnMiss(int X, int Y)
        {
            soundMiss.Play();
        }

        private Vector2 ComputeNewDirection()
        {
            Random rnd = new Random();
            Vector2 ret = new Vector2(-1.0f + 2.0f * (float)rnd.NextDouble(), -1.0f + 2.0f * (float)rnd.NextDouble());
            ret.Normalize();

            return ret;
        }
    }
}
